﻿package net.wangit.jdbc;


import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.HashSet;
import java.util.Iterator;

import net.wangit.framework.config.DebugSettings;
import net.wangit.jdbc.config.JDBCSettings;
import net.wangit.jdbc.util.JDBCExceptionReporter;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

public abstract class BatcherImpl implements Batcher {
	private JDBCSettings js;
	private static int openResultSetCount;
	protected static final Log log = LogFactory.getLog(BatcherImpl.class);
	private final DBImplementor db;
	private PreparedStatement batchUpdate;
	private String batchUpdateSQL;
	private HashSet<PreparedStatement> statementsToClose = new HashSet<PreparedStatement>();
	private HashSet<ResultSet> resultSetsToClose = new HashSet<ResultSet>();

	public BatcherImpl(DBImplementor db) {
		this.db = db;
		this.js = db.getJDBCSettings();
	}

	protected PreparedStatement getBatchStatement() {
		return this.batchUpdate;
	}

	public PreparedStatement prepareStatement(String sql) throws SQLException,
			JDBCException {
		return getPreparedStatement(this.db.getConnection(), sql, false);
	}

	public PreparedStatement prepareQueryStatement(String sql,
			boolean scrollable) throws SQLException, JDBCException {
		Connection conn = this.db.getConnection();
		PreparedStatement ps = getPreparedStatement(conn, sql, scrollable);
		setStatementFetchSize(ps);
		this.statementsToClose.add(ps);
		return ps;
	}

	public void abortBatch(SQLException sqle) {
		PreparedStatement ps = this.batchUpdate;
		this.batchUpdate = null;
		this.batchUpdateSQL = null;
		try {
			closeStatement(ps);
		} catch (SQLException e) {
			JDBCExceptionReporter.logExceptions(e);
		}
	}

	public ResultSet getResultSet(PreparedStatement ps) throws SQLException {
		ResultSet rs = ps.executeQuery();
		this.resultSetsToClose.add(rs);
		logOpenResults();
		return rs;
	}

	public void closeQueryStatement(PreparedStatement ps, ResultSet rs)
			throws SQLException {
		this.statementsToClose.remove(ps);
		if (rs != null)
			this.resultSetsToClose.remove(rs);
		try {
			if (rs != null) {
				rs.close();
			}
			logCloseResults();
		} finally {
			closeQueryStatement(ps);
		}
	}

	public PreparedStatement prepareBatchStatement(String sql)
			throws SQLException, JDBCException {
		if (!(sql.equals(this.batchUpdateSQL))) {
			executeBatch();
			this.batchUpdate = prepareStatement(sql);
			this.batchUpdateSQL = sql;
		}
		return this.batchUpdate;
	}

	public void executeBatch() throws JDBCException {
		if (this.batchUpdate != null) {
			PreparedStatement ps = this.batchUpdate;
			this.batchUpdate = null;
			this.batchUpdateSQL = null;
			try {
				try {
					doExecuteBatch(ps);
				} finally {
					closeStatement(ps);
				}
			} catch (SQLException sqle) {
				throw new JDBCException("Could not execute JDBC batch update",
						sqle);
			}
		}
	}

	public void closeStatement(PreparedStatement ps) throws SQLException {
		closePreparedStatement(ps);
	}

	private void closeQueryStatement(PreparedStatement ps) throws SQLException {
		try {
			if (ps.getMaxRows() != 0)
				ps.setMaxRows(0);
			if (ps.getQueryTimeout() != 0)
				ps.setQueryTimeout(0);
		} catch (Exception e) {
			log.warn("exception clearing maxRows/queryTimeout", e);
			ps.close();
			return;
		}

		closeStatement(ps);
	}

	@SuppressWarnings("unchecked")
	public void closeStatements() {
		try {
			if (this.batchUpdate != null)
				this.batchUpdate.close();
		} catch (SQLException sqle) {
			log.warn("Could not close a JDBC prepared statement", sqle);
		}
		this.batchUpdate = null;
		this.batchUpdateSQL = null;

		Iterator iter = this.resultSetsToClose.iterator();
		while (iter.hasNext()) {
			try {
				logCloseResults();
				((ResultSet) iter.next()).close();
			} catch (SQLException e) {
				log.warn("Could not close a JDBC result set", e);
			}
		}
		this.resultSetsToClose.clear();

		iter = this.statementsToClose.iterator();
		while (iter.hasNext()) {
			try {
				closeQueryStatement((PreparedStatement) iter.next());
			} catch (SQLException e) {
				log.warn("Could not close a JDBC statement", e);
			}
		}
		this.statementsToClose.clear();
	}

	protected abstract void doExecuteBatch(
			PreparedStatement paramPreparedStatement) throws SQLException,
			JDBCException;

	private static void logOpenResults() {
		if (log.isTraceEnabled())
			openResultSetCount += 1;
	}

	private static void logCloseResults() {
		if (log.isTraceEnabled())
			openResultSetCount -= 1;
	}

	protected JDBCSettings getJDBCSettings() {
		return this.js;
	}

	private PreparedStatement getPreparedStatement(Connection conn, String sql,
			boolean scrollable) throws SQLException {
		if ((scrollable) && (!(this.js.isScrollable()))) {
			log.error("this db didn't supported scrollable");
			throw new UnsupportedOperationException(
					"tried to grab a scrollable result set");
		}

		if (DebugSettings.isDebugEnabled(log, this.db)) {
			log.debug("sql: " + sql);
		}

		try {
			if (scrollable) {
				return conn.prepareStatement(sql,
						ResultSet.TYPE_SCROLL_INSENSITIVE,
						ResultSet.CONCUR_READ_ONLY);
			}

			return conn.prepareStatement(sql);
		} catch (SQLException sqle) {
			sqle.printStackTrace();
			JDBCExceptionReporter.logExceptions(sqle);
			throw sqle;
		}
	}

	private void closePreparedStatement(PreparedStatement ps)
			throws SQLException {
		try {
			if (ps != null)
				ps.close();
		} catch (SQLException sqle) {
			JDBCExceptionReporter.logExceptions(sqle);
			throw sqle;
		}
	}

	private void setStatementFetchSize(PreparedStatement statement)
			throws SQLException {
		int statementFetchSize = this.js.getFetchSize();
		if (statementFetchSize < 0) {
			return;
		}

		statement.setFetchSize(statementFetchSize);
	}
}